package model

import (
	"encoding/json"
	"errors"
	"fmt"
	"strconv"
	"time"

	client "github.com/influxdata/influxdb1-client/v2"
)

type Invoke struct {
	Id              string    `json:"requestId" valid:"-"`
	Creator         string    `json:"creator" valid:"-"` //这里的creator是回调回来的用户id，也就是主用户id，并不是子用户id
	FuncId          string    `json:"functionId" valid:"-"`
	FuncName        string    `json:"functionName" valid:"-"`
	GroupId         string    `json:"groupId" valid:"-"`
	GroupName       string    `json:"groupName" valid:"-"`
	VersionName     string    `json:"versionName" valid:"-"`
	RuntimeDuration int       `json:"runtimeDuration" valid:"-"`
	InvokeDuration  int       `json:"invokeDuration" valid:"-"`
	InvokeMode      string    `json:"invokeMode" valid:"-"`
	InvokeType      string    `json:"invokeType" valid:"-"`
	InvokeSource    string    `json:"invokeSource" valid:"-"`
	MemoryUsage     int       `json:"memoryUsage" valid:"-"`
	MemoryLimit     int       `json:"memoryLimit" valid:"-"`
	StartTime       time.Time `json:"startTime" valid:"-"`
	ReturnMsg       string    `json:"returnMsg" valid:"-"`
	ReturnCode      int       `json:"returnCode" valid:"-"`
	Checksum        string    `json:"checksum" valid:"-"`
	Log             string    `json:"log" valid:"-"`
}

type BillInfo struct {
	UserId  string
	Count   int64 // 单位：万次
	Compute float64
}

const (
	InfluxDBName          = "tenant"
	InfluxInvokeTable     = "fcs_invoke"
	InfluxRetentionPolicy = "fcs"
)

func GetInvokeList(query *LogQuery) ([]*Invoke, error) {
	sql := getListQuerySql(query)
	sql = fmt.Sprintf("select * from %s %s ORDER BY time DESC LIMIT %d OFFSET %d",
		getCombineTableName(), sql, query.PageSize, query.PageSize*(query.PageNo-1))
	c, err := client.NewHTTPClient(client.HTTPConfig{
		Addr:     Base.InfluxdbHost,
		Username: Base.InfluxdbUser,
		Password: Base.InfluxdbP6d,
	})
	if err != nil {
		fmt.Println("Error creating InfluxDB Client: ", err.Error())
		return nil, err
	}
	defer func() {
		_ = c.Close()
	}()
	q := client.NewQuery(sql, InfluxDBName, "")
	if response, err := c.Query(q); err == nil && response.Error() == nil {
		return getInvokeList(response), nil
	} else {
		return nil, err
	}
}

func getInvokeList(response *client.Response) []*Invoke {
	invokeList := make([]*Invoke, 0)
	if len(response.Results) == 0 {
		return invokeList
	}
	for _, series := range response.Results[0].Series {
		columnsMap := getColumnsMap(series.Columns)
		for _, value := range series.Values {
			invoke := new(Invoke)
			if value[columnsMap["id"]] != nil {
				invoke.Id = value[columnsMap["id"]].(string)
			}
			if value[columnsMap["creator"]] != nil {
				invoke.Creator = value[columnsMap["creator"]].(string)
			}
			if value[columnsMap["func_id"]] != nil {
				invoke.FuncId = value[columnsMap["func_id"]].(string)
			}
			invoke.StartTime, _ = time.Parse(time.RFC3339, value[columnsMap["time"]].(string))
			if value[columnsMap["return_code"]] != nil {
				invoke.ReturnCode, _ = strconv.Atoi(value[columnsMap["return_code"]].(string))
			}
			if value[columnsMap["memory_usage"]] != nil {
				memory64, _ := value[columnsMap["memory_usage"]].(json.Number).Int64()
				invoke.MemoryUsage = int(memory64)
			}
			if value[columnsMap["invoke_type"]] != nil {
				invoke.InvokeType = value[columnsMap["invoke_type"]].(string)
			}
			if value[columnsMap["invoke_duration"]] != nil {
				invoke64, _ := value[columnsMap["invoke_duration"]].(json.Number).Int64()
				invoke.InvokeDuration = int(invoke64)
			}
			if value[columnsMap["runtime_duration"]] != nil {
				runtime64, _ := value[columnsMap["runtime_duration"]].(json.Number).Int64()
				invoke.RuntimeDuration = int(runtime64)
			}
			if value[columnsMap["return_msg"]] != nil {
				invoke.ReturnMsg = value[columnsMap["return_msg"]].(string)
			}
			if value[columnsMap["log"]] != nil {
				invoke.Log = value[columnsMap["log"]].(string)
			}
			invokeList = append(invokeList, invoke)
		}
	}
	return invokeList
}

func getBillList(response *client.Response) []*BillInfo {
	billList := make([]*BillInfo, 0)
	if len(response.Results) == 0 {
		return billList
	}
	for _, series := range response.Results[0].Series {
		creator := series.Tags["creator"]
		columnsMap := getColumnsMap(series.Columns)
		for _, value := range series.Values {
			bill := new(BillInfo)
			bill.UserId = creator
			if value[columnsMap["count"]] != nil {
				bill.Count, _ = value[columnsMap["count"]].(json.Number).Int64()
			}
			if value[columnsMap["compute"]] != nil {
				bill.Compute, _ = value[columnsMap["compute"]].(json.Number).Float64()
			}
			billList = append(billList, bill)
		}
	}
	return billList
}

func GetInvokeListCount(query *LogQuery) (int64, error) {
	sql := getListQuerySql(query)
	sql = fmt.Sprintf("select count(id) from %s %s", getCombineTableName(), sql)
	c, err := client.NewHTTPClient(client.HTTPConfig{
		Addr:     Base.InfluxdbHost,
		Username: Base.InfluxdbUser,
		Password: Base.InfluxdbP6d,
	})
	if err != nil {
		fmt.Println("Error creating InfluxDB Client: ", err.Error())
		return 0, err
	}
	defer func() {
		_ = c.Close()
	}()
	q := client.NewQuery(sql, InfluxDBName, "")
	if response, err := c.Query(q); err == nil && response.Error() == nil && response.Results != nil &&
		response.Results[0].Series != nil {
		return response.Results[0].Series[0].Values[0][1].(json.Number).Int64()
	}
	return 0, err
}

func getListQuerySql(query *LogQuery) string {

	// 由于日志处理需要时间，所以最新日志延迟一段时间显示
	sql := fmt.Sprintf(`where "func_id"='%s' and time <= '%s' `, query.FuncId, time.Now().Add(-time.Duration(Base.LogDelayTime)*time.Second).Format(time.RFC3339))
	if query.StartTime != 0 {
		sql = sql + fmt.Sprintf(`and time >= '%s' `, time.Unix(query.StartTime, 0).Format(time.RFC3339))
	}
	if query.EndTime != 0 {
		sql = sql + fmt.Sprintf(`and time <= '%s' `, time.Unix(query.EndTime, 0).Format(time.RFC3339))
	}
	if query.RequestId != "" {
		sql = sql + fmt.Sprintf(`and "id" = '%s' `, query.RequestId)
	}
	if query.Status != "" {
		switch query.Status {
		case InvokeSuccess:
			sql = sql + `and "return_code" = '200'`
		case InvokeFailure:
			sql = sql + `and "return_code" != '200'`
		}
	}
	return sql
}

func getColumnsMap(columns []string) map[string]int {
	columnsMap := make(map[string]int)
	for i, column := range columns {
		columnsMap[column] = i
	}
	return columnsMap
}

func GetInvokeMonthData(creator string) (int64, float64, error) {
	now := time.Now()
	monthStartTime := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
	sql := fmt.Sprintf(`select count(id), sum(compute_power) from %s where time>='%s' and time<='%s' and "creator"='%s'`,
		getCombineTableName(), monthStartTime.Format(time.RFC3339), now.Format(time.RFC3339), creator)
	c, err := client.NewHTTPClient(client.HTTPConfig{
		Addr:     Base.InfluxdbHost,
		Username: Base.InfluxdbUser,
		Password: Base.InfluxdbP6d,
	})
	if err != nil {
		fmt.Println("Error creating InfluxDB Client: ", err.Error())
		return 0, 0, err
	}
	defer func() {
		_ = c.Close()
	}()
	q := client.NewQuery(sql, InfluxDBName, "")
	if response, err := c.Query(q); err == nil && response.Error() == nil {
		if response.Results[0].Series == nil || len(response.Results[0].Series) == 0 {
			return 0, 0, nil
		}
		count, err := response.Results[0].Series[0].Values[0][1].(json.Number).Int64()
		if err == nil {
			compute, err := response.Results[0].Series[0].Values[0][2].(json.Number).Float64()
			return count, compute, err
		}
		return 0, 0, err
	} else {
		return 0, 0, err
	}
}

func GetMetricQuery(query *MetricQuery) ([]*MetricResult, error) {
	sql, err := getMetricsSql(query)
	logger.Info("GetMetricQuery: sql is %v", sql)
	if err != nil {
		_ = logger.Error("GetMetricQuery: get sql error, %v", err)
		return nil, err
	}
	metricResultList := make([]*MetricResult, 0)
	c, err := client.NewHTTPClient(client.HTTPConfig{
		Addr:     Base.InfluxdbHost,
		Username: Base.InfluxdbUser,
		Password: Base.InfluxdbP6d,
	})
	if err != nil {
		fmt.Println("Error creating InfluxDB Client: ", err.Error())
		return nil, err
	}
	defer func() {
		_ = c.Close()
	}()
	q := client.NewQuery(sql, InfluxDBName, "")
	if response, err := c.Query(q); err == nil && response.Error() == nil {
		if len(response.Results) == 0 || len(response.Results[0].Series) == 0 {
			return metricResultList, nil
		}
		for _, value := range response.Results[0].Series[0].Values {
			mr := new(MetricResult)
			mr.Time, _ = time.Parse(time.RFC3339, value[0].(string))
			if value[1] != nil {
				mr.Data, _ = value[1].(json.Number).Float64()
			}
			metricResultList = append(metricResultList, mr)
		}
	} else {
		return nil, err
	}
	return metricResultList, nil
}

func getMetricsSql(query *MetricQuery) (string, error) {
	var sql string
	switch query.MetricName {
	case InvokeCount:
		sql = `SELECT COUNT(id) FROM %s where "func_id"='%s' and time>='%s' and time<='%s' GROUP BY time(%dm) ORDER BY time`
	case InvokeDuration:
		sql = `SELECT MEAN(runtime_duration) FROM %s where "func_id"='%s' and time>='%s' and time<='%s' GROUP BY time(%dm) ORDER BY time`
	case InvokeMemory:
		sql = `SELECT MEAN(memory_usage) FROM %s where "func_id"='%s' and time>='%s' and time<='%s' GROUP BY time(%dm) ORDER BY time`
	case InvokeErrorCount:
		sql = `SELECT COUNT(id) FROM %s where "func_id"='%s' and time>='%s' and time<='%s' and "return_code"!='200' GROUP BY time(%dm) ORDER BY time`
	default:
		return "", errors.New("no match metrics name")
	}
	sql = fmt.Sprintf(sql, getCombineTableName(), query.FuncId, time.Unix(query.StartTime, 0).Format(time.RFC3339),
		time.Unix(query.EndTime, 0).Format(time.RFC3339), query.Period)
	return sql, nil
}

func InfluxQuery(sql string) (*client.Response, error) {
	c, err := client.NewHTTPClient(client.HTTPConfig{
		Addr:     Base.InfluxdbHost,
		Username: Base.InfluxdbUser,
		Password: Base.InfluxdbP6d,
	})
	if err != nil {
		fmt.Println("Error creating InfluxDB Client: ", err.Error())
		return nil, err
	}
	defer func() {
		_ = c.Close()
	}()
	q := client.NewQuery(sql, InfluxDBName, "")
	if response, err := c.Query(q); err == nil && response.Error() == nil {
		return response, nil
	} else {
		return nil, err
	}
}

func GetInvokesForMonitor(startTime, endTime int64) ([]*Invoke, error) {
	sql := fmt.Sprintf("select * from %s where time>='%s' and time<'%s'", getCombineTableName(),
		time.Unix(startTime, 0).Format(time.RFC3339), time.Unix(endTime, 0).Format(time.RFC3339))
	if response, err := InfluxQuery(sql); err == nil {
		return getInvokeList(response), nil
	} else {
		return nil, err
	}
}

func GetBillInfo(startTime, endTime int64) ([]*BillInfo, error) {
	sql := fmt.Sprintf("select count(id)/10000 as count, sum(compute_power) as compute from %s where time>='%s' "+
		"and time<='%s' group by creator",
		getCombineTableName(), time.Unix(startTime, 0).Format(time.RFC3339), time.Unix(endTime, 0).Format(time.RFC3339))
	if response, err := InfluxQuery(sql); err == nil {
		return getBillList(response), nil
	} else {
		return nil, err
	}
}

func getCombineTableName() string {
	return "\"" + InfluxRetentionPolicy + "\"." + InfluxInvokeTable
}
